package pl.matt.media.protocol;

import java.awt.Dimension;

import javax.media.Buffer;
import javax.media.Control;
import javax.media.Format;
import javax.media.IncompatibleSourceException;
import javax.media.ResourceUnavailableException;
import javax.media.control.QualityControl;
import javax.media.format.VideoFormat;
import javax.media.protocol.BufferTransferHandler;
import javax.media.protocol.DataSource;
import javax.media.protocol.PullBufferDataSource;
import javax.media.protocol.PushBufferDataSource;
import javax.media.protocol.PushBufferStream;

import pl.matt.io.utils.FileUtils;
import pl.matt.media.strategy.FrameNamingStrategy;

import com.sun.media.codec.video.jpeg.NativeEncoder;

/**
 * @author mateusz
 * 
 */
public class JPEGDataSourceHandler implements BufferTransferHandler {

	private DataSource source;
	private PushBufferStream pushStrms[] = null;
	private Buffer readBuffer;
	private int imgWidth;
	private int imgHeight;
	private NativeEncoder nativeEncoder;
	private float quality = 1.0f;
	private FrameNamingStrategy frameNamingStrategy;
	

	public JPEGDataSourceHandler(FrameNamingStrategy frameNamingStrategy, VideoFormat videoFormat) throws ResourceUnavailableException {
		this.frameNamingStrategy = frameNamingStrategy;
		this.imgHeight = videoFormat.getSize().height;
		this.imgWidth = videoFormat.getSize().width;
		initJPEGNativeEncoder(videoFormat);
	}

	/**
	 * Sets the media source this MediaHandler should use to obtain content.
	 */
	public void setSource(DataSource source) throws IncompatibleSourceException {
		// Different types of DataSources need to handled differently.
		if (source instanceof PushBufferDataSource) {
			pushStrms = ((PushBufferDataSource) source).getStreams();

			// Set the transfer handler to receive pushed data from the push
			pushStrms[0].setTransferHandler(this);

		} else if (source instanceof PullBufferDataSource) {

			// This handler only handles push buffer datasource.
			throw new IncompatibleSourceException();
		}

		this.source = source;
		readBuffer = new Buffer();
	}

	/**
	 * This will get called when there's data pushed from the
	 * PushBufferDataSource.
	 */
	public void transferData(PushBufferStream stream) {
		try {
			stream.read(readBuffer);
		} catch (Exception e) {
			e.printStackTrace();
			return;
		}

		// Just in case contents of data object changed by some other thread
		Buffer inBuffer = (Buffer) (readBuffer.clone());

		// Check for end of stream
		if (readBuffer.isEOM()) {
			return;
		}

		// Do useful stuff or wait
		useFrameData(inBuffer);
	}

	/**
	 * Called on each new frame buffer
	 */
	private void useFrameData(Buffer inBuffer) {
		try {
			// vfw://0 can deliver nulls
			if (inBuffer.getData() != null) {
				byte[] b = fetchJpeg(inBuffer);
				String path = frameNamingStrategy.getName(inBuffer.getTimeStamp());
				FileUtils.write(path, b);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * inicjuje encoder, który przekoduje klatki do JPEgów
	 * 
	 * @param vfin
	 * @throws ResourceUnavailableException
	 */
	private void initJPEGNativeEncoder(Format vfin) throws ResourceUnavailableException {
		// This encoder need multiples of 8 - use another if a problem
		if (imgWidth % 8 != 0 || imgHeight % 8 != 0) {
			throw new IllegalArgumentException("Image sizes not /8");
		}

		VideoFormat vfout = new VideoFormat("jpeg", new Dimension(imgWidth, imgHeight), imgWidth * imgHeight * 3,
				Format.byteArray, -1F);

		nativeEncoder = new NativeEncoder();
		nativeEncoder.setInputFormat(vfin);

		nativeEncoder.setOutputFormat(vfout);

		nativeEncoder.open();

		Control cs[] = (Control[]) nativeEncoder.getControls();
		for (int i = 0; i < cs.length; i++) {
			if (cs[i] instanceof QualityControl) {
				QualityControl qc = (QualityControl) cs[i];
				qc.setQuality(quality);
				break;
			}
		}
	}

	/**
	 * przekodowuje dane z bufora do jpegowej tablicy bajtów
	 * 
	 * @param inBuffer
	 * @return
	 * @throws Exception
	 */
	private byte[] fetchJpeg(Buffer inBuffer) throws Exception {
		Buffer outBuffer = new Buffer(); // may need new to keep threadsafe
		// if extended
		nativeEncoder.process(inBuffer, outBuffer);
		int lengthF = outBuffer.getLength();
		byte[] b = new byte[lengthF];
		System.arraycopy(outBuffer.getData(), 0, b, 0, lengthF);
		return b;
	}

	public void start() {
		try {
			source.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void stop() {
		try {
			source.stop();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
